#ifndef __client_gpio_kenny_kfjiweur89u83r33e3e__
#define __client_gpio_kenny_kfjiweur89u83r33e3e__

#include <functional>
#include <memory>
#include <map>

#include <hude/base/base_type.hpp>
#include <hude/base/optional.hpp>
#include <hude/aio/context.hpp>

#include "virt-gpio.hpp"

namespace ha
{
    namespace details
    {
        /*********************************************************************
         * @brief gpio类型
         */
        enum gpio_type_t { emGPIO_EMPTY, emGPIO_REAL, emGPIO_VIRT };

        /*********************************************************************
         * @brief gpio操作实现
         */
        template< gpio_type_t _type > struct _gpio_imp_t;

        /*********************************************************************
         * @brief gpio实现
         */
        template<>
        struct _gpio_imp_t< emGPIO_EMPTY >
        {
            using this_t   = _gpio_imp_t< emGPIO_EMPTY >;
            using dir_t    = ha::brick::_gpio_t::direction_t;

            struct pin_t
            {
                bool enable_;
                bool level_;
                dir_t dir_;
            };

            using map_t    = std::map< uint16_t, pin_t >;

            map_t map_;

            _gpio_imp_t() {}


            hude::optional_t< bool > enable( uint16_t index );
            bool enable( uint16_t index, bool value );

            bool direction( uint16_t index, dir_t dir );
            hude::optional_t< dir_t > direction( uint16_t index );

            hude::optional_t< bool > value( uint16_t index );
            bool value( uint16_t index, bool level );
        };

        /*********************************************************************
         * @brief 本地gpio实现
         */
        template<>
        struct _gpio_imp_t< emGPIO_REAL >
        {
            using this_t  = _gpio_imp_t< emGPIO_REAL >;
            using dir_t   = ha::brick::_gpio_t::direction_t;
            using gpios_t = std::map< uint16_t, std::shared_ptr< FILE > >;

            gpios_t gpios_;

            hude::optional_t< bool > enable( uint16_t index );
            bool enable( uint16_t index, bool value );

            bool direction( uint16_t index, dir_t dir );
            hude::optional_t< dir_t > direction( uint16_t index );

            hude::optional_t< bool > value( uint16_t index );
            bool value( uint16_t index, bool level );

            static bool gwrite( FILE* fptr, const std::string& content );
            static bool gwrite( const std::string& file, const std::string& content );

            static hude::optional_t< std::string > gread( FILE* fptr );
            static hude::optional_t< std::string > gread( const std::string& file );
        };

        /*********************************************************************
         * @brief 代理gpio实现
         */
        template<>
        struct _gpio_imp_t< emGPIO_VIRT >
        {
            using this_t   = _gpio_imp_t< emGPIO_VIRT >;
            using dir_t    = ha::brick::_gpio_t::direction_t;
            using client_t = brick::gpio_client_t;

            client_t client_;

            _gpio_imp_t( const char* name = "/tmp/dev/ttyGPIO", int speed = 115200U ) : client_( name, speed ) {}

            hude::optional_t< bool > enable( uint16_t index );
            bool enable( uint16_t index, bool value );

            bool direction( uint16_t index, dir_t dir );
            hude::optional_t< dir_t > direction( uint16_t index );

            hude::optional_t< bool > value( uint16_t index );
            bool value( uint16_t index, bool level );
        };
    } //namespace details

    /*********************************************************************
     * @brief ...
     */
    using gpio_type_t = details::gpio_type_t;

    /*********************************************************************
     * @brief gpio简化别名
     */
    template< gpio_type_t _type >
    using gpio_t = details::_gpio_imp_t< _type >;

    /*********************************************************************
     * @brief ...
     */
    namespace details
    {
        /*********************************************************************
         * @brief ...
         */
        hude::optional_t< std::string > _gpio_imp_t< emGPIO_REAL >::gread( FILE* fptr )
        {
            enum : size_t { emSIZ = 128U };

            int ret;

            hude::buffer_t buffer( emSIZ );
            std::string result;

            while ( 0 == feof( fptr ) )
            {
                ret = fread( &(buffer[0]), 1, emSIZ, fptr );

                if( ret > 0 )
                {
                    std::copy( buffer.begin(), buffer.begin() + ret, std::back_inserter( result ) );
                }

                if( ret < (int)emSIZ ) return hude::optional_null_t{};
            }
            
            return std::move( result );
        }

        /*********************************************************************
         * @brief ...
         */
        hude::optional_t< std::string > _gpio_imp_t< emGPIO_REAL >::gread( const std::string& file )
        {
            FILE* fp = std::fopen( file.c_str(), "r" );
            if ( fp == nullptr ) return hude::optional_null_t{};
            std::unique_ptr< FILE, void(*)( FILE* ) > ptr( fp, []( FILE* fp ) { fclose( fp ); } );

            return gread( fp );
        }

        /*********************************************************************
         * @brief ...
         */
        bool _gpio_imp_t< emGPIO_REAL >::gwrite( FILE* fptr, const std::string& content )
        {
            size_t wrt;

            wrt = std::fwrite( content.c_str(), sizeof( char ), content.size(), fptr );

            bool ret = ( content.size() == wrt );

            return ret;
        }

        /*********************************************************************
         * @brief ...
         */
        bool _gpio_imp_t< emGPIO_REAL >::gwrite( const std::string& file, const std::string& content )
        {
            FILE* fp = std::fopen( file.c_str(), "w" );
            if ( fp == nullptr ) { return false; }
            std::unique_ptr< FILE, void(*)( FILE* ) > ptr( fp, []( FILE* fp ) { fclose( fp ); } );

            return gwrite( fp, content );
        }

        /*********************************************************************
         * @brief ...
         */
        hude::optional_t< bool > _gpio_imp_t< emGPIO_REAL >::enable( uint16_t index )
        {
            return gpios_.find( index ) != gpios_.end();
        }

        /*********************************************************************
         * @brief ...
         */
        bool _gpio_imp_t< emGPIO_REAL >::enable( uint16_t index, bool value )
        {
            auto itr = gpios_.find( index );

            if( value != ( itr == gpios_.end() ) ) return false;

            if( value )
            {
                FILE* fp;

                if( ! gwrite( "/sys/class/gpio/export", hude::format_t( "%u", index ) ) )
                    goto err;

                if( ! gwrite( hude::format_t( "/sys/class/gpio/gpio%u/edge", index ), "both" ) )
                    goto err;

                if( ! gwrite( hude::format_t( "/sys/class/gpio/gpio%u/direction", index ), "in" ) )
                    goto err;

                fp = fopen( hude::format_t( "/sys/class/gpio/gpio%u/value", index ).c_str(), "rw" );
                if( fp == nullptr || ! gwrite( fp, "0" ) ) goto err;

                gpios_.emplace( index, std::shared_ptr< FILE >{ fp, []( FILE* p ) { fclose( p ); } } );

                return true;
            } else {
                gpios_.erase( index );

                gwrite( "/sys/class/gpio/unexport", hude::format_t( "%u", index ) );

                return true;
            }

        err:
            gwrite( "/sys/class/gpio/unexport", hude::format_t( "%u", index ) );

            return false;
        }

        /*********************************************************************
         * @brief ...
         */
        bool _gpio_imp_t< emGPIO_REAL >::direction( uint16_t index, dir_t dir )
        {
            auto itr = gpios_.find( index );
            if( itr == gpios_.end() ) return false;

            return gwrite( hude::format_t( "/sys/class/gpio/gpio%u/direction", index ), ( dir == dir_t::emDIN )? "in" : "out" );
        }

        /*********************************************************************
         * @brief ...
         */
        hude::optional_t< _gpio_imp_t< emGPIO_REAL >::dir_t > _gpio_imp_t< emGPIO_REAL >::direction( uint16_t index )
        {
            auto itr = gpios_.find( index );
            if( itr == gpios_.end() ) return hude::optional_null_t{};

            auto ret = gread( hude::format_t( "/sys/class/gpio/gpio%u/direction", index ) );
            if( ! ret ) return hude::optional_null_t{};

            return ( *ret == "in" )? dir_t::emDIN : dir_t::emDOUT;
        }

        /*********************************************************************
         * @brief ...
         */
        hude::optional_t< bool > _gpio_imp_t< emGPIO_REAL >::value( uint16_t index )
        {
            auto itr = gpios_.find( index );
            if( itr == gpios_.end() ) return hude::optional_null_t{};

            auto ret = gread( itr->second.get() );
            if( ! ret ) return hude::optional_null_t{};

            return ret->at( 0 ) == '1';
        }

        /*********************************************************************
         * @brief ...
         */
        bool _gpio_imp_t< emGPIO_REAL >::value( uint16_t index, bool level )
        {
            auto itr = gpios_.find( index );
            if( itr == gpios_.end() ) return false;

            return gwrite( itr->second.get(), level? "1" : "0" );
        }

        /*********************************************************************
         * @brief gpio
         */
        hude::optional_t< bool > _gpio_imp_t< emGPIO_EMPTY >::enable( uint16_t index )
        {
            auto itr = map_.find( index );

            return ( itr != map_.end() );
        }

        /*********************************************************************
         * @brief gpio
         */
        bool _gpio_imp_t< emGPIO_EMPTY >::enable( uint16_t index, bool value )
        {
            if( value )
            {
                return map_.emplace( index, pin_t{ value, false, dir_t::emDIN } ).second;
            } else {
                auto itr = map_.find( index );

                if( itr == map_.end() )
                {
                    return false;
                } else {
                    map_.erase( itr );

                    return true;
                }
            }
        }

        /*********************************************************************
         * @brief gpio
         */
        bool _gpio_imp_t< emGPIO_EMPTY >::direction( uint16_t index, dir_t dir )
        {
            auto itr = map_.find( index );

            if( itr == map_.end() ) return false;

            itr->second.dir_ = dir;

            return true;
        }

        /*********************************************************************
         * @brief gpio
         */
        hude::optional_t< _gpio_imp_t< emGPIO_EMPTY >::dir_t > _gpio_imp_t< emGPIO_EMPTY >::direction( uint16_t index )
        {
            auto itr = map_.find( index );

            return ( itr == map_.end() )?
                hude::optional_t< dir_t >{} :
                hude::optional_t< dir_t >{ itr->second.dir_ };
        }

        /*********************************************************************
         * @brief gpio
         */
        hude::optional_t< bool > _gpio_imp_t< emGPIO_EMPTY >::value( uint16_t index )
        {
            auto itr = map_.find( index );

            return ( itr == map_.end() )?
                hude::optional_t< bool >{} :
                hude::optional_t< bool >{ itr->second.level_ };
        }

        /*********************************************************************
         * @brief gpio
         */
        bool _gpio_imp_t< emGPIO_EMPTY >::value( uint16_t index, bool level )
        {
            auto itr = map_.find( index );

            if( itr == map_.end() ) return false;

            itr->second.level_ = level;

            return true;
        }

        /*********************************************************************
         * @brief gpio
         */
        hude::optional_t< bool > _gpio_imp_t< emGPIO_VIRT >::enable( uint16_t index )
        {
            return client_.enable( index );
        }

        /*********************************************************************
         * @brief gpio
         */
        bool _gpio_imp_t< emGPIO_VIRT >::enable( uint16_t index, bool value )
        {
            return client_.enable( index, value );
        }

        /*********************************************************************
         * @brief gpio
         */
        bool _gpio_imp_t< emGPIO_VIRT >::direction( uint16_t index, dir_t dir )
        {
            return client_.direction( index, dir );
        }

        /*********************************************************************
         * @brief gpio
         */
        hude::optional_t< _gpio_imp_t< emGPIO_VIRT >::dir_t > _gpio_imp_t< emGPIO_VIRT >::direction( uint16_t index )
        {
            return client_.direction( index );
        }

        /*********************************************************************
         * @brief gpio
         */
        hude::optional_t< bool > _gpio_imp_t< emGPIO_VIRT >::value( uint16_t index )
        {
            return client_.value( index );
        }

        /*********************************************************************
         * @brief gpio
         */
        bool _gpio_imp_t< emGPIO_VIRT >::value( uint16_t index, bool level )
        {
            return client_.value( index, level );
        }
    } //namespace details
} // namespace ha

#endif // __client_gpio_kenny_kfjiweur89u83r33e3e__

